package com.fuint.minio.util;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Iterables;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName : MinIOUtil
 * @Description : minIO 文件操作工具类
 * @Author : wzq
 * @Date: 2022-01-04 17:04
 */
@Slf4j
@Component
public class MinIOUtil {
    @Autowired
    private MinioClient minioClient;

    /**
     * 检查存储桶是否存在
     *
     * @param bucketName 存储桶名称
     * @return
     */
    
    public boolean bucketExists(String bucketName){
        log.debug("检查存储桶是否存在：bucketName：{}",bucketName);
        boolean found =false;
        try {
            found =
                minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        }catch (Exception e){
            log.error("检查桶出现异常", e);
        }
        log.debug("检查存储桶是否存在，结果：{}",found);
        return found;
    }

    /**
     * 创建存储桶
     *
     * @param bucketName 存储桶名称
     */
    
    public void makeBucket(String bucketName)throws Exception  {
            log.debug("创建存储桶：bucketName：{}",bucketName);
            try {
                minioClient.makeBucket(
                        MakeBucketArgs.builder()
                                .bucket(bucketName)
                                .build());
            }catch (ErrorResponseException e){
                if (e.getMessage().equals("Your previous request to create the named bucket succeeded and you already own it.")){
                    log.info("桶已存在" );
                }
            }
            log.debug("创建存储桶完成");
    }

    /**
     * 列出所有存储桶名称
     *
     * @return
     */
    
    public List<String> listBucketNames() {
        log.debug("列出所有存储桶名称 开始");
        List<Bucket> bucketList = listBuckets();
        List<String> bucketListName = new ArrayList<>();
        for (Bucket bucket : bucketList) {
            bucketListName.add(bucket.name());
        }
        log.debug("列出所有存储桶名称 返回：{}", JSON.toJSONString(bucketListName));
        return bucketListName;
    }

    /**
     * 列出所有存储桶
     *
     * @return
     */
    
    public List<Bucket> listBuckets(){
        log.debug("列出所有存储桶 开始");
        List<Bucket> list = null;
        try {
            list = minioClient.listBuckets();
        }catch (Exception e){
            log.error("列出所有存储桶 失败", e);
        }
        log.debug("列出所有存储桶 返回：{}", JSON.toJSONString(list));
        return list;
    }


    /**
     * 删除存储桶
     *
     * @param bucketName 存储桶名称
     * @return
     */
    
    public boolean removeBucket(String bucketName) throws Exception {
        log.debug("删除存储桶：bucketName：{}",bucketName);
        boolean flag = bucketExists(bucketName);
        if (flag) {
            Iterable<Result<Item>> myObjects = listObjects(bucketName);
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                // 有对象文件，则删除失败
                if (item.size() > 0) {
                    return false;
                }
            }
            // 删除存储桶，注意，只有存储桶为空时才能删除成功。
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
            flag = bucketExists(bucketName);
            if (!flag) {
                return true;
            }
        }
        return false;
    }

    /**
     * 列出存储桶中的所有对象名称
     *
     * @param bucketName 存储桶名称
     * @return
     */
    
    public List<String> listObjectNames(String bucketName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        log.debug("列出存储桶中的所有对象名称：bucketName：{}",bucketName);
        List<String> listObjectNames = new ArrayList<>();
        boolean flag = bucketExists(bucketName);
        if (flag) {
            Iterable<Result<Item>> myObjects = listObjects(bucketName);
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                listObjectNames.add(item.objectName());
            }
        }else{
            listObjectNames.add("存储桶不存在");
        }
        log.debug("列出存储桶中的所有对象名称 返回：{}",JSON.toJSONString(listObjectNames));
        return listObjectNames;
    }


    /**
     * 列出存储桶中的所有对象
     *
     * @param bucketName 存储桶名称
     * @return
     */
    
    public Iterable<Result<Item>> listObjects(String bucketName) {
        log.debug("列出存储桶中的所有对象：bucketName：{}",bucketName);
        boolean flag = bucketExists(bucketName);
        if (flag) {
            return minioClient.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).build());
        }
        log.debug("列出存储桶中的所有对象名称：桶内未找到文件信息");
        return null;
    }

    /**
     *  设置桶策略
     * @param
     * @author      wzq
     * @date        2021/11/17 15:03
     */
    
    public void setBucketPolicy(String bucketName,String configJson) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        log.debug("设置桶策略：bucketName：{}，策略：{}",bucketName,configJson);
            minioClient.setBucketPolicy(
                    SetBucketPolicyArgs.builder().bucket(bucketName).config(configJson).build()
            );
        log.info("桶：{} 设置策略完成",bucketName);
    }
    /**
     * 获取桶策略
     */
    
    public String getBucketPolicy(String bucketName) throws ServerException, InsufficientDataException, ErrorResponseException, BucketPolicyTooLargeException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            return minioClient.getBucketPolicy(
                    GetBucketPolicyArgs.builder().bucket(bucketName).build()
            );
        }
        return null;
    }

    /**
     * 文件上传 以流的形式
     */
    
    public String putObject(String bucketName, InputStream fileInputStream, String filename) throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        log.debug("文件上传 以流的形式：bucketName：{}，fileName：{}",bucketName,filename);
        // 根据filename后缀判断contentType
        String contextType = this.getContextType(filename);
        ObjectWriteResponse object = minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName)
                        .object(filename)
                        .contentType(contextType)
                        .stream(fileInputStream,fileInputStream.available(),-1)
                        .build());

        return object.versionId();
    }

    /**
     * 根据filename后缀判断contentType
     */
    private String getContextType(String filename){

        String contentType = "application/octet-stream";

        String last = StringUtils.substringAfterLast(filename,".");
        if (StringUtils.isNotBlank(last)){
            if ("xlxs".equals(last)){
                contentType = "application/vnd.ms-excel;charset=utf-8";
            }else if ("png".equalsIgnoreCase(last)){
                contentType = "image/png";
            }else if ("jpg".equalsIgnoreCase(last)){
                contentType = "image/jpeg";
            }else if ("json".equals(last)){
                contentType = "application/json";
            }
            else if ("mp4".equals(last)||"avi".equals(last)){
                contentType = "video/mp4";
            }
        }
        log.debug("getContextType的值： contentType：{}",contentType);
        return contentType;
    }

    /**
     * 获取带过期时间的文件访问路径
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param duration 期间
     * @param timeUnit 时间单位
     * @return
     */
    
    public String getObjectUrl(String bucketName, String objectName,Integer duration,TimeUnit timeUnit) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        boolean flag = bucketExists(bucketName);
        String url = "";
        if (flag) {
            url = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(duration, timeUnit)
                            .build());
        }
        return url;
    }


    /**
     * 删除一个对象
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     */
    
    public void removeObject(String bucketName, String objectName,String versionId) {
        log.debug("删除一个对象：bucketName：{}，objectName：{},versionId：{}",bucketName,objectName,versionId);
        boolean flag = bucketExists(bucketName);
        try {
            if (flag) {
            log.debug("执行删除");
                minioClient.removeObject(
                        RemoveObjectArgs.builder().bucket(bucketName).object(objectName).versionId(versionId).build());
            }
        }catch (Exception e){
            log.error("删除对象失败", e);
        }
    }


    /**
     * 以流的形式获取一个文件对象
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @return
     */
    
    public InputStream getObject(String bucketName, String objectName, String versionId) {
        log.debug("以流的形式获取一个文件对象：bucketName：{}，objectName：{},versionId：{}",bucketName,objectName,versionId);
           try {
               StatObjectResponse statObject = statObject(bucketName, objectName,versionId);
               if (statObject != null && statObject.size() > 0) {
                   return minioClient.getObject(
                                   GetObjectArgs.builder()
                                           .bucket(bucketName)
                                           .object(objectName)
                                           .versionId(versionId)
                                           .build());
               }else {
                   log.error("在桶：{} 中没有找到fileName为:{} 的文件,版本号:{}",bucketName,objectName,versionId);
               }
           }catch (Exception e){
               e.printStackTrace();
           }
        return null;
    }

    /**
     * 下载文件到指定路径
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param versionId 版本哈，设置为null就下载最新版本的
     * @param filePath 下载到的位置
     * @return
     */
    
    public void downloadObject(String bucketName, String objectName, String versionId,String filePath) {
        log.debug("下载文件到指定路径：bucketName：{}，objectName：{},versionId：{}",bucketName,objectName,versionId);
            try {
                StatObjectResponse statObject = statObject(bucketName, objectName,versionId);
                if (statObject != null && statObject.size() > 0) {
                    minioClient.downloadObject(
                            DownloadObjectArgs.builder()
                                    .bucket(bucketName)
                                    .object(objectName)
                                    .filename(filePath)
                                    .versionId(versionId)
                                    .build()
                    );
                }else {
                    log.error("在桶：{} 中没有找到fileName为:{} 的文件,版本号:{}",bucketName,objectName,versionId);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            log.debug("文件：{} 下载完成",objectName);
    }

    /**
     * 判断桶内是否存在数据
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param versionId 版本号
     * @return
     */
    
    public boolean existInBucket(String bucketName, String objectName,String versionId) {
        log.debug("判断桶内是否存在数据：bucketName：{}，objectName：{},versionId:{}",bucketName,objectName,versionId);
         return this.statObject(bucketName, objectName, versionId) == null;
    }
    /**
     * 获取对象的元数据带VersionId
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param versionId  存储桶里对象的版本号
     * @return
     */
    
    public StatObjectResponse statObject(String bucketName, String objectName,String versionId){
        log.debug("获取对象的元数据带VersionId：bucketName：{}，objectName：{},versionId：{}",bucketName,objectName,versionId);
        boolean flag = bucketExists(bucketName);
        StatObjectResponse statObjectResponse = null;
        try {
            if (flag) {
                statObjectResponse = minioClient.statObject(
                        StatObjectArgs.builder().bucket(bucketName).object(objectName).versionId(versionId).build());
            }
        }catch (Exception e){
            e.printStackTrace();
            log.error("在桶：{} 中没有找到fileName为:{} 的文件,versionId：{}",bucketName,objectName,versionId);
        }
        return statObjectResponse;
    }

    /**
     * 删除指定桶的多个文件对象
     *
     * @param bucketName  存储桶名称
     * @param objectNames 含有要删除的多个object名称的迭代器对象
     * @return
     */
    
    public boolean removeObject(String bucketName, List<String> objectNames) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        if (!bucketExists(bucketName)) {
            throw new ServerException("桶不存在");
        }
            List<DeleteObject> objects = new LinkedList<>();
            for (int i = 0; i < objectNames.size(); i++) {
                objects.add(new DeleteObject(objectNames.get(i)));
            }
            Iterable<Result<DeleteError>> results =
                    minioClient.removeObjects(
                            RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                log.error("文件：{} 删除失败,失败原因:{}", error.objectName(),error.message());
            }
        return Iterables.isEmpty(results);
    }

    /**
     * 以流的形式获取一个文件对象（断点下载）
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param offset     起始字节的位置
     * @param length     要读取的长度 (可选，如果无值则代表读到文件结尾)
     * @return
     */
    
    public InputStream getObject(String bucketName, String objectName, long offset, Long length) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            StatObjectResponse statObject = statObject(bucketName, objectName,null);
            if (statObject != null && statObject.size() > 0) {
                return minioClient.getObject(
                                GetObjectArgs.builder()
                                        .bucket(bucketName)
                                        .object(objectName)
                                        .offset(offset)
                                        .length(length)
                                        .build());
            }
        }
        return null;
    }

    /**
     * 上传文件
     */
    public String upload(String path , String bucketName, String objectName ) throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        log.debug("上传文件 path：{} bucketName：{},objectName：{}",path,bucketName,objectName);
        // 判断类型
        ObjectWriteResponse object = minioClient.uploadObject(
                UploadObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
//                        .contentType(this.getContextType(objectName))
                        .filename(path)
                        .build()
        );
        log.debug("文件：{}上传完成,桶：{},版本号：{}",objectName,bucketName,object.versionId());
        return object.versionId();
    }

    /**
     * copy 文件
     * @param copyBucketName 拷贝到哪个桶
     * @param objectName 文件名称
     * @param sourceBucketName 源桶
     * @param sourceObjectName 源文件名称
     */
    
    public String copyObject(String copyBucketName, String objectName,String sourceBucketName, String sourceObjectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        log.debug("文件copy开始：源桶：{}，源文件：{}，拷贝到 桶：{}，文件：{}",copyBucketName,objectName,sourceBucketName,sourceObjectName);
        // 复制文件
        ObjectWriteResponse objectWriteResponse = minioClient.copyObject(
                CopyObjectArgs.builder()
                        .bucket(copyBucketName)
                        .object(objectName)
                        .source(
                                CopySource.builder()
                                        .bucket(sourceBucketName)
                                        .object(sourceObjectName)
                                        .build())
                        .build());
        log.debug("文件copy完成：源桶：{}，源文件：{}，拷贝到 桶：{}，文件：{}，版本号：{}",
                copyBucketName,objectName,sourceBucketName,sourceObjectName,objectWriteResponse.versionId());
       return objectWriteResponse.versionId();
    }

    /**
     * 启用桶版本控制
     */
    
    public void setBucketVersioning(String bucketName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
       log.debug("启用桶版本控制：bucketName：{}",bucketName);
        minioClient.setBucketVersioning(SetBucketVersioningArgs.builder()
                .bucket(bucketName)
                .config(new VersioningConfiguration(VersioningConfiguration.Status.ENABLED,null))
                .build());
        log.info("桶：{} 开启了版本控制！",bucketName);
    }
    /**
     * 获取桶版本是否开起
     * @return
     */
    
    public VersioningConfiguration getBucketVersioning(String bucketName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return minioClient.getBucketVersioning(GetBucketVersioningArgs.builder()
                .bucket(bucketName)
                .build());
    }


    /**
     *
     *  文件使用byte数组方式上传 大文件会有可能出现内存溢出的情况，所以大文件慎用这个方法
     * @param bucketName 桶的名称
     * @param fileName 文件名称带后缀
     * @param bytes 文件的byte数组
     * @return 版本id
     */
    public String uploadByteByPutObject(String bucketName, String fileName, byte[] bytes){
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().object(fileName)
                    .bucket(bucketName)
                    .contentType(getContextType(fileName))
                    .stream(new ByteArrayInputStream(bytes),bytes.length,-1).build();
            ObjectWriteResponse objectWriteResponse = minioClient.putObject(objectArgs);

            return objectWriteResponse.versionId();
        }catch (Exception e){
            log.error("文件使用byte数组方式上传异常：{}", fileName, e);
        }
        return null;
    }

    /**
     * 文件上传成功后调用这个方法，不需要minio的token 生成一个带有过期时间访问的url地址
     * @param bucketName 桶名称
     * @param fileName 文件名称
     * @param duration 过期时长
     * @param timeUnit 过期时长的单位
     * @return
     * @throws Exception
     */
    public String getUrl(String bucketName, String fileName,Integer duration,TimeUnit timeUnit){
        Map<String, String> reqParams = new HashMap<>();
        reqParams.put("response-content-type", getContextType(fileName));
        String url ="";
        try {
            url =
                    minioClient.getPresignedObjectUrl(
                            GetPresignedObjectUrlArgs.builder()
                                    .method(Method.GET)
                                    .bucket(bucketName)
                                    .object(fileName)
                                    .expiry(duration, timeUnit)
                                    .extraQueryParams(reqParams)
                                    .build());
        }catch (Exception e){
            log.error("文件上传成功后获取带有过期时间的图片连接地址异常：{}", fileName, e);
        }

        return url;
    }

    /**
     * 获取视频需要的url
     * @param ip_host minio host:port
     * @param bucketName 桶名称
     * @param objectName 文件名称
     * @return 返回小写 类型
     */
    public String getVideoUrl(String ip_host,String bucketName,String objectName) {
        return ip_host + "/minio/download/" + bucketName+"/"+objectName + "?token=";
    }
}
